﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Projektarbete
{
    class MapHandler
    {
        public static Tile[][] map;
        public static float[][] lightMap;
        public static bool[][] visitedMap;
        private static Texture2D tileset;
        private static Texture2D black;

        public static Point mapDimensions;

        public static Point topLeft;
        public static Point bottomRight;

        private static Point cullingTopLeft;
        private static Point cullingBottomRight;

        private static int visualRange = 10;

        private static Rectangle tempRect;
        private static Vector2 tempVector2;
        private static Point tempPoint;

        /// <summary>
        /// Creates the map system with the following starting map
        /// </summary>
        /// <param name="map">Starting map to use</param>
        /// <param name="tileset">Tileset to use</param>
        public static void SetupMap(Tile[][] startingMap, Texture2D currentTileset)
        {
            map = startingMap;
            tileset = currentTileset;

            topLeft = Point.Zero;
            bottomRight = Point.Zero;
            cullingTopLeft = Point.Zero;
            cullingBottomRight = Point.Zero;

            tempRect = new Rectangle(0, 0, 24, 24);

            mapDimensions = new Point(map[0].Length, map.Length);

            lightMap = new float[mapDimensions.Y][];
            visitedMap = new bool[mapDimensions.Y][];

            for (int y = 0; y < mapDimensions.Y; y++)
            {
                lightMap[y] = new float[mapDimensions.X];
                visitedMap[y] = new bool[mapDimensions.X];

                for (int x = 0; x < mapDimensions.X; x++)
                {
                    lightMap[y][x] = 0.0f;
                    visitedMap[y][x] = false;
                }
            }

            black = Global.LoadTexture2D("black");
        }

        /// <summary>
        /// Changes the tile of a given position
        /// </summary>
        /// <param name="tile">New tile which should be put at the position</param>
        /// <param name="position">Position to put it at</param>
        public static void ChangeTile(Tile tile, Point position)
        {
            map[position.Y][position.X] = tile;
        }

        /// <summary>
        /// Changes the tile of a given position
        /// </summary>
        /// <param name="tile">tile to change</param>
        /// <param name="newTile">tile to change the old tile into</param>
        public static void ChangeTile(Tile tile, Tile newTile)
        {
            tile = newTile;
        }

        public static void ChangeMap(Tile[][] newMap)
        {
            map = newMap;
            mapDimensions = new Point(map[0].Length, map.Length);
        }

        //public static System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();

        /// <summary>
        /// Updates the lightmap
        /// </summary>
        /// <param name="playerPosition">TILED Position of the player</param>
        public static void UpdateLightmap()
        {
            //stopwatch.Reset();
            //stopwatch.Start();
            for (int y = 0; y < mapDimensions.Y; y++)
            {
                for (int x = 0; x < mapDimensions.X; x++)
                {
                    lightMap[y][x] = 0.0f;
                }
            }

            tempPoint = Player.GetTiledPositionToPoint();

            lightMap[tempPoint.Y][tempPoint.X] = 1.0f;

            UpdateTheShiz(Player.GetTiledPositionToPoint(), visualRange); //Kalkuera ljus. Namnet var temporärt men låter bra så det är nice

            tempPoint = Player.GetTiledPositionToPoint(); //sätt tillbaka tempPoint till spelarens position eftersom att UpdateTheShiz använder den (postProcess())

            //Fråga David om du vill veta
            cullingTopLeft.X = (int)MathHelper.Clamp(tempPoint.X - 17, 0, mapDimensions.X);
            cullingTopLeft.Y = (int)MathHelper.Clamp(tempPoint.Y - 13, 0, mapDimensions.Y);

            cullingBottomRight.X = (int)MathHelper.Clamp(tempPoint.X + 18, 0, mapDimensions.X);
            cullingBottomRight.Y = (int)MathHelper.Clamp(tempPoint.Y + 14, 0, mapDimensions.Y);

            for (int y = cullingTopLeft.Y; y < cullingBottomRight.Y; y++)
            {
                for (int x = cullingTopLeft.X; x < cullingBottomRight.X; x++)
                {
                    if (lightMap[y][x] != 1.0)
                        if (visitedMap[y][x])
                            lightMap[y][x] = 0.4f;
                }
            }

            //stopwatch.Stop();
            //System.Diagnostics.Debug.WriteLine("Lighting time: " + stopwatch.ElapsedMilliseconds.ToString() + "," + stopwatch.ElapsedTicks.ToString());
        }

        #region light stuff
        private static readonly int permissiveness = 2;
        private static readonly float outerAngleSweep = 0.2f; //TODO: fine-tune

        private struct AngleRange
        {
            public float startAngle;
            public float endAngle;

            public AngleRange(float startAngle, float endAngle)
            {
                this.startAngle = startAngle;
                this.endAngle = endAngle;
            }
        }

        private static LinkedList<AngleRange> blockedAngles = new LinkedList<AngleRange>();
        
        private static bool isBlockedAngle(float angle)
        {
            foreach (AngleRange currentAngle in blockedAngles)
                if (angle >= currentAngle.startAngle && angle <= currentAngle.endAngle)
                    return true;

            return false;
        }

        private static LinkedList<Point> litUpTiles = new LinkedList<Point>();

        public static void UpdateTheShiz(Point origin, int radius)
        {
            for (bool flip = false; ; flip = true)
            {
                for (int xDirection = -1; xDirection <= 1; xDirection += 2)
                {
                    for (int yDirection = -1; yDirection <= 1; yDirection += 2)
                    {
                        blockedAngles.Clear();

                        bool lineOpen = true;

                        for (int line = 1; line <= radius && lineOpen; line++)
                        {
                            lineOpen = false;

                            for (int cell = 0; cell <= line; cell++)
                            {
                                int x;
                                int y;

                                if (flip)
                                {
                                    x = line * xDirection;
                                    y = cell * yDirection;
                                }
                                else
                                {
                                    x = cell * xDirection;
                                    y = line * yDirection;
                                }

                                float range = 1.0f / (float)(line + 1);

                                float startAngle = range * (float)(cell);
                                float midAngle = startAngle + range * 0.5f;
                                float endAngle = startAngle + range;

                                startAngle -= range * outerAngleSweep;
                                endAngle += range * outerAngleSweep;

                                int iBlockedAngles = 0;

                                if (isBlockedAngle(startAngle))
                                    iBlockedAngles++;
                                if (isBlockedAngle(midAngle))
                                    iBlockedAngles++;
                                if (isBlockedAngle(endAngle))
                                    iBlockedAngles++;

                                Point coordinate = new Point(origin.X + x, origin.Y + y);

                                if (coordinate.X < 0)
                                    continue;
                                if (coordinate.X >= mapDimensions.X)
                                    continue;
                                if (coordinate.Y < 0)
                                    continue;
                                if (coordinate.Y >= mapDimensions.Y)
                                    continue;

                                if (iBlockedAngles < permissiveness)
                                {
                                    lightMap[coordinate.Y][coordinate.X] = 1.0f;
                                    visitedMap[coordinate.Y][coordinate.X] = true;
                                    litUpTiles.AddLast(coordinate);
                                    lineOpen = true;
                                }

                                if (map[coordinate.Y][coordinate.X].BlocksVision)
                                    blockedAngles.AddFirst(new AngleRange(startAngle, endAngle));
                            }
                        }
                    }
                }
                if (flip)
                    break;
            }

            postProcess();
        }

        private static Point[] eightDir = { new Point(-1, 0), new Point(1, 0), new Point(0, -1), new Point(0, 1) };

        private static void postProcess()
        {
            foreach (Point position in litUpTiles)
            {
                if (Collision.TileType(map[position.Y][position.X].getTextureID) == TileTypes.Passable) //Om det är mark
                {
                    for (int x = 0; x < 4; x++) //kolla igenom intilliggande tiles
                    {
                        tempPoint.X = position.X + eightDir[x].X; //sätt tempPos till grannens position
                        tempPoint.Y = position.Y + eightDir[x].Y;

                        if (Collision.TileType(map[tempPoint.Y][tempPoint.X].getTextureID) == TileTypes.Impassable) //om grannen är en vägg
                        {
                            lightMap[tempPoint.Y][tempPoint.X] = 1.0f; //gör grannen synlig
                        }
                    }
                }
            }

            litUpTiles.Clear();
        }
        #endregion

        public static void DrawMap(SpriteBatch spriteBatch, Vector2 tiledPosition)
        {
            for (int y = cullingTopLeft.Y; y < cullingBottomRight.Y; y++)
            {
                for (int x = cullingTopLeft.X; x < cullingBottomRight.X; x++)
                {
                    tempRect.X = map[y][x].getDrawRow * 24;
                    tempRect.Y = map[y][x].getDrawColumn * 24;

                    tempVector2.X = x * 24;
                    tempVector2.Y = y * 24;
                    //spriteBatch.Draw(tileset, tempVector2, tempRect, Color.White * lightMap[y][x]);
                    spriteBatch.Draw(tileset, tempVector2, tempRect, Color.White);
                    tempVector2.X = map[y][x].getTextureID;
                }
            }
        }

        public static void DrawLightmap(SpriteBatch spriteBatch)
        {
            for (int y = cullingTopLeft.Y; y < cullingBottomRight.Y; y++)
            {
                for (int x = cullingTopLeft.X; x < cullingBottomRight.X; x++)
                {
                    tempRect.X = x * 24;
                    tempRect.Y = y * 24;

                    spriteBatch.Draw(black, tempRect, Color.White * (1 - lightMap[y][x]));
                }
            }
        }
    }
}